/**
 * @(#)com.book.sys.Test
 * 2009-5-9
 * Copyright 2009
 * Book软件公司, 版权所有 违者必究
 */
package com.site.base.dao;

import org.hibernate.*;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.persister.entity.SingleTableEntityPersister;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;


/**
 * 所有DAO的基类,对load,get,save,saveOrUpdate,update肮脏delete等操作都提供了三个类似的方法，其
 * 格式形如action(..),actionEntity(..),actionEntity(entityName,..)。其中第一个方法以要求实体类配置时
 * 是未使用实体名的方法。如果实体类配置使用了实体名，则必须采用actionEntity的方法，如果采用和类名同名
 * 的实体名，则采用actionEntity(..)，如果采用指定实体名的操作，则必须使用actionEntity(entityName,..).
 * 如User实体类采用了指定实体名，其配置文件如下所示：
 * <pre>
 * <hibernate-mapping package="com.book.domain.sys">
 * <!-- 这儿使用了和类名相同的实体名,即User,可以调用getEntity(id),saveEntity(entity)该方法以
 * 默认实体名的方式操作-->
 * <class name="User" table="t_user" entity-name="User" catalog="book">
 * <id name="id" type="java.lang.String">
 * <column name="user_id" length="36" />
 * <generator class="assigned" />
 * </id>
 * <property name="companyId" type="java.lang.String">
 * <column name="company_id" length="36" />
 * </property>
 * ...
 * </class>
 * <!-- 这儿使用了自定义的实体名,SimpleUser,可以调用getEntity("SimpleUser",id),saveEntity("SimpleUser",entity)该方法以
 * 默认实体名的方式操作-->
 * <class name="User" table="t_company" entity-name="SimpleUser" catalog="book">
 * <id name="id" type="java.lang.String">
 * <column name="user_id" length="36" />
 * <generator class="assigned" />
 * </id>
 * <property name="isOnline" type="java.lang.String">
 * <column name="IS_ONLINE" length="1"/>
 * </property>
 * <property name="lastActiveTime" type="java.lang.Long">
 * <column name="LAST_ACTIVE_TIME" length="64"/>
 * </property>
 * </class>
 * </hibernate-mapping>
 * </pre>
 *
 * @author 陈雄华
 * @version 1.0
 */
public class BaseDao<T> {
    /**
     * Session工厂
     */
    protected SessionFactory sessionFactory;
    /**
     * Hibernate 模板类
     */
    protected HibernateTemplate hibernateTemplate;
    /**
     * 操作的领域对象的类型
     */
    protected Class<T> entityClass;

    /*日志记录器*/
    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final int BATCH_SIZE = 20;

    /**
     * 在构造函数中获取Dao所操作的领域对象类型
     */
    public BaseDao() {
        Type genType = getClass().getGenericSuperclass();
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        entityClass = (Class) params[0];
    }

    /**
     * 调用该方法加载延迟加载的数据
     *
     * @param obj 需要缓存的对象
     */
    public void initialize(Object obj) throws ObjectNotFoundException {
        hibernateTemplate.initialize(obj);
    }

    /**
     * 根据ID加载领域对象
     *
     * @param id 领域对象的Id
     * @return 领域对象
     */
    public T load(Serializable id) {
        return (T) hibernateTemplate.load(entityClass, id);
    }

    /**
     * 根据ID加载领域对象
     *
     * @param id 领域对象的Id
     * @return 领域对象
     */
    public T loadEntity(Serializable id) {
        return loadEntity(entityClass.getSimpleName(), id);
    }

    /**
     * 根据ID加载领域对象
     *
     * @param id 领域对象的Id
     * @return 领域对象
     */
    public T loadEntity(String entityName, Serializable id) {
        return (T) hibernateTemplate.load(entityName, id);
    }

    /**
     * 根据ID获取领域对象
     *
     * @param id 领域对象的Id
     * @return 领域对象
     */
    public T get(Serializable id) {
        return hibernateTemplate.get(entityClass, id);
    }

    /**
     * 根据entityName和id获得领域对象
     *
     * @param id
     */
    public T getEntity(Serializable id) {
        return getEntity(entityClass.getSimpleName(), id);
    }

    /**
     * 根据entityName和id获得领域对象
     *
     * @param entityName
     * @param id
     */
    public T getEntity(String entityName, Serializable id) {
        return (T) hibernateTemplate.get(entityName, id);
    }

    /**
     * 保存新增或修改的对象.
     */
    public void save(final T obj) {
        getSession().save(obj);
    }

    /**
     * 保存新增或修改的对象.
     */
    public void saveEntity(final T entity) {
        saveEntity(entity.getClass().getSimpleName(), entity);
    }

    /**
     * 保存新增或修改的对象.
     */
    public void saveEntity(String entityName, final T entity) {
        getSession().save(entityName, entity);
    }

    /**
     * 保存或更新领域对象
     *
     * @param entity
     */
    public void saveOrUpdate(T entity) {
        hibernateTemplate.saveOrUpdate(entity);
    }

    /**
     * 保存或更新领域对象
     *
     * @param entity
     */
    public void saveOrUpdateEntity(T entity) {
        saveOrUpdateEntity(entityClass.getSimpleName(), entity);
    }

    /**
     * 保存或更新领域对象
     *
     * @param entity
     */
    public void saveOrUpdateEntity(String entityName, T entity) {
        hibernateTemplate.saveOrUpdate(entityName, entity);
    }

    /**
     * 批量保存
     *
     * @param objList
     */
    public void saveBatch(List<T> objList) {
        if (objList == null || objList.size() == 0) {
            return;
        }
        int i = 0;
        List<T> batchObjList = new ArrayList<T>();
        for (T obj : objList) {
            save(obj);
            batchObjList.add(obj);
            if (++i % BATCH_SIZE == 0) {
                flush();
                evict(batchObjList);
                batchObjList.clear();
            }
        }
    }

    /**
     * 将对象列表从一级缓存中清除
     * @param objList
     */
    public void evict(List<T> objList) {
        for (T obj : objList) {
            evict(obj);
        }
    }

    /**
     * 将对象从一级缓存中清除
     *
     * @param obj
     */
    public void evict(Object obj) {
        hibernateTemplate.evict(obj);
    }

    /**
     * 批量保存
     *
     * @param objList
     */
    public void saveEntityBatch(List<T> objList) {
        saveEntityBatch(entityClass.getSimpleName(), objList);
    }

    /**
     * 批量保存
     *
     * @param objList
     */
    public void saveEntityBatch(String entityName, List<T> objList) {
        if (objList == null || objList.size() == 0) {
            return;
        }
        int i = 0;
        for (T obj : objList) {
            saveEntity(entityName, obj);
            if (++i % 20 == 0) { //同时到数据并清除缓存
                flush();
                clear();
            }
        }
    }

    /**
     * 批量保存或更新
     *
     * @param objList
     */
    public void saveOrUpdateEntityBatch(List<T> objList) {
        if (objList == null || objList.size() == 0) {
            return;
        }
        int i = 0;
        for (T obj : objList) {
            saveOrUpdateEntity(entityClass.getSimpleName(), obj);
            if (++i % 20 == 0) { //同时到数据并清除缓存
                flush();
                clear();
            }
        }
    }

    /**
     * 批量保存或更新
     *
     * @param objList
     */
    public void saveOrUpdateEntityBatch(String entityName, List<T> objList) {
        if (objList == null || objList.size() == 0) {
            return;
        }
        int i = 0;
        for (T obj : objList) {
            saveOrUpdateEntity(entityName, obj);
            if (++i % 20 == 0) { //同时到数据并清除缓存
                flush();
                clear();
            }
        }
    }

    /**
     * 更改领域对象
     *
     * @param obj 实体对象
     */
    public void update(T obj) {
        hibernateTemplate.update(obj);
    }

    /**
     * 根据实体名更新实体对象
     *
     * @param entityName
     * @param obj
     */
    public void updateEntity(T obj) {
        updateEntity(entityClass.getSimpleName(), obj);
    }

    /**
     * 根据实体名更新实体对象
     *
     * @param entityName
     * @param obj
     */
    public void updateEntity(String entityName, T obj) {
        hibernateTemplate.update(entityName, obj);
    }

    /**
     * 删除对象.
     *
     * @param entity 对象必须是session中的对象或含id属性的transient对象.
     */
    public void delete(final T entity) {
        getSession().delete(entity);
    }

    /**
     * 删除对象.
     *
     * @param entity 对象必须是session中的对象或含id属性的transient对象.
     */
    public void deleteEntity(final T entity) {
        deleteEntity(entity.getClass().getSimpleName(), entity);
    }

    /**
     * 删除对象.
     *
     * @param entity 对象必须是session中的对象或含id属性的transient对象.
     */
    public void deleteEntity(String entityName, final T entity) {
        getSession().delete(entityName, entity);
    }

    /**
     * 批量更新业务对象
     *
     * @param objList 需要删除的实体集合
     */
    public void updateBatch(List<T> objList) {
        if (objList == null || objList.size() == 0) {
            return;
        }
        int i = 0;
        for (T obj : objList) {
            update(obj);
            // 将本批插入的对象立即写入数据库并释放内存
            // 如果要将很多对象持久化，你必须通过经常的调用 flush() 以及稍后调用 clear() 来控制第一级缓存的大小             *
            if (++i % 20 == 0) {
                flush();
                clear();
            }
        }
    }

    /**
     * 批量更新业务对象
     *
     * @param objList 需要删除的实体集合
     */
    public void updateEntityBatch(List<T> objList) {
        updateEntityBatch(entityClass.getSimpleName(), objList);
    }

    /**
     * 批量更新业务对象
     *
     * @param objList 需要删除的实体集合
     */
    public void updateEntityBatch(String entityName, List<T> objList) {
        if (objList == null || objList.size() == 0) {
            return;
        }
        int i = 0;
        for (T obj : objList) {
            updateEntity(entityName, obj);
            // 将本批插入的对象立即写入数据库并释放内存
            // 如果要将很多对象持久化，你必须通过经常的调用 flush() 以及稍后调用 clear() 来控制第一级缓存的大小             *
            if (++i % 20 == 0) {
                flush();
                clear();
            }
        }
    }

    /**
     * 批量删除实体
     *
     * @param objList
     */
    public void deleteBatch(List<T> objList) {
        if (objList == null || objList.size() == 0) {
            return;
        }
        int i = 0;
        for (T obj : objList) {
            delete(obj);
            if (++i % BATCH_SIZE == 0) {
                flush();
                clear();
            }
        }
    }

    public T meger(T t) {
        return hibernateTemplate.merge(t);
    }

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    /**
     * 采用@Autowired按类型注入SessionFactory,当有多个SesionFactory的时候Override本函数.
     *
     * @param sessionFactory
     */
    @Autowired
    public void setSessionFactory(final SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
        hibernateTemplate = new HibernateTemplate(sessionFactory);
    }

    public Session getSession() {
//        return sessionFactory.getCurrentSession();
        return SessionFactoryUtils.getSession(sessionFactory, true);
    }

    /**
     * 根据主键删除记录
     *
     * @param id
     */
    public void deleteById(Serializable id) {
        Map map = hibernateTemplate.getSessionFactory().getAllClassMetadata();
        SingleTableEntityPersister p = (SingleTableEntityPersister) map.get(entityClass.getName());
        String idName = p.getIdentifierPropertyName();
        String hql = " delete from " + entityClass.getName() + " where " + idName + "='" + id + "'";
        hibernateTemplate.bulkUpdate(hql);
    }

    /**
     * 执行HQL查询
     *
     * @param hql
     */
    public List<T> find(String hql) {
        return hibernateTemplate.find(hql);
    }

    /**
     * 按Criteria查询对象列表.
     *
     * @param criterions 数量可变的Criterion.
     */
    public List<T> find(final Criterion... criterions) {
        return createCriteria(criterions).list();
    }

    /**
     * 按HQL查询对象列表.
     *
     * @param values 数量可变的参数
     */
    public List<T> find(final String hql, final Object... values) {
        return createQuery(hql, values).list();
    }

    public List<T> find(final String hql, Map<String, Object> values) {
        return createQuery(hql, values).list();
    }

    /**
     * 按属性查找对象列表,匹配方式为相等.
     */
    public List<T> findByProp(final String propertyName, final Object value) {
        Assert.hasText(propertyName, "propertyName不能为空");
        Criterion criterion = Restrictions.eq(propertyName, value);
        return find(criterion);
    }

    /**
     * 获取全部对象.
     */
    public List<T> getAll() {
        return find();
    }

    /**
     * 按属性查找唯一对象,匹配方式为相等.
     */
    public T getByAkProp(final String propertyName, final Object value) {
        Assert.hasText(propertyName, "propertyName不能为空");
        Criterion criterion = Restrictions.eq(propertyName, value);
        return (T) createCriteria(criterion).uniqueResult();
    }

    /**
     * 按HQL查询唯一对象.
     */
    public Object getObject(final String hql, final Object... values) {
        return createQuery(hql, values).uniqueResult();
    }

    /**
     * 按HQL查询唯一对象.
     */
    public Object getObject(final String hql, Map<String, Object> values) {
        return createQuery(hql, values).uniqueResult();
    }

    /**
     * 按HQL查询Integer类型结果.
     */
    public Integer getInt(final String hql, final Object... values) {
        Object obj = getObject(hql, values);
        if (obj instanceof Long) {
            return ((Long) obj).intValue();
        }
        return (Integer) obj;
    }

    /**
     * 按HQL查询Integer类型结果.
     */
    public Integer getInt(final String hql, Map<String, Object> values) {
        Object obj = getObject(hql, values);
        if (obj instanceof Long) {
            return ((Long) obj).intValue();
        }
        return (Integer) obj;
    }

    /**
     * 按HQL查询Long类型结果.
     */
    public Long getLong(final String hql, final Object... values) {
        Object obj = getObject(hql, values);
        if (obj instanceof Integer) {
            return ((Integer) obj).longValue();
        }
        return (Long) obj;
    }

    /**
     * 按HQL查询Long类型结果.
     */
    public Long getLong(final String hql, Map<String, Object> values) {
        Object obj = getObject(hql, values);
        if (obj instanceof Integer) {
            return ((Integer) obj).longValue();
        }
        return (Long) obj;
    }

    /**
     * 根据查询HQL与参数列表创建Query对象.
     * 返回对象类型不是Entity时可用此函数灵活查询.
     *
     * @param values 数量可变的参数
     */
    public Query createQuery(final String queryString, final Object... values) {
        Query query = getSession().createQuery(queryString);
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                query.setParameter(i, values[i]);
            }
        }
        return query;
    }


    /**
     * 根据查询HQL与参数列表创建Query对象.
     * 返回对象类型不是Entity时可用此函数灵活查询.
     *
     * @param namedParamMap
     */
    public Query createQuery(final String queryString, Map<String, Object> namedParamMap) {
        Assert.hasText(queryString, "queryString不能为空");

        Query query = getSession().createQuery(queryString);
        if (namedParamMap != null && !namedParamMap.isEmpty()) {
            for (Map.Entry<String, Object> entry : namedParamMap.entrySet()) {
                applyNamedParameterToQuery(query, entry.getKey(), entry.getValue());
            }
        }
        return query;
    }


    /**
     * 根据Criterion条件创建Criteria.
     * 返回对象类型不是Entity时可用此函数灵活查询.
     *
     * @param criterions 数量可变的Criterion.
     */
    public Criteria createCriteria(final Criterion... criterions) {
        Criteria criteria = getSession().createCriteria(entityClass);
        for (Criterion c : criterions) {
            criteria.add(c);
        }
        return criteria;
    }

    /**
     * 判断对象的属性值在数据库内是否唯一.
     * 在修改对象的情景下,如果属性新修改的值(value)等于属性原来的值(orgValue)则不作比较.
     */
    public boolean isPropertyUnique(final String propertyName, final Object newValue, final Object orgValue) {
        if (newValue == null || newValue.equals(orgValue))
            return true;
        Object object = getByAkProp(propertyName, newValue);
        return (object == null);
    }

    /**
     * 取得对象的主键名.
     */
    public String getIdName() {
        ClassMetadata meta = getSessionFactory().getClassMetadata(entityClass);
        Assert.notNull(meta, "Class " + entityClass.getSimpleName() + " not define in HibernateSessionFactory.");
        return meta.getIdentifierPropertyName();
    }

    /**
     * @param queryObject
     * @param paramName
     * @param value
     * @throws HibernateException
     */
    private void applyNamedParameterToQuery(Query queryObject, String paramName, Object value)
            throws HibernateException {
        if (value instanceof Collection) {
            queryObject.setParameterList(paramName, (Collection) value);
        } else if (value instanceof Object[]) {
            queryObject.setParameterList(paramName, (Object[]) value);
        } else {
            queryObject.setParameter(paramName, value);
        }
    }

    /**
     * @param hql
     * @param args 绑定到HQL中的参数
     * @param argTypes 参数的SQL类型 (即java.sql.Types中定义的常量)
     * 注意：慎用，因为会造成二级缓存和数据库的不一致，使用批量更改或删除，
     * 相当于直接发送一条SQL语句，会绕过Hibernate二级缓存的维护。
     */
    public void execute(final String hql, Object... objs) {
        this.hibernateTemplate.bulkUpdate(hql, objs);
    }

    /**
     * 清除当前Session中的本地缓存
     */
    public void clear() {
        hibernateTemplate.clear();
    }

    /**
     * 冲洗当前Session的本地缓存
     */
    public void flush() {
        hibernateTemplate.flush();
    }

    /**
     * 设置二级缓存模式
     * @param cacheMode
     */
    public void setCacheMode(CacheMode cacheMode){
        getSession().setCacheMode(cacheMode);
    }

}
